import { keccak256 } from "ethereum-cryptography/keccak";
import { hexToBytes as _hexToBytes, bytesToHex as _bytesToHex } from "ethereum-cryptography/utils";

export function assertHex(input: string, expectedLength?: number) {
  if (!input || !input.startsWith("0x")) throw new Error(`Hex字符串必须以0x开头: ${input}`);
  const hex = input.slice(2);
  if (!/^[0-9a-fA-F]*$/.test(hex)) throw new Error(`非法Hex内容: ${input}`);
  if (expectedLength !== undefined && hex.length !== expectedLength * 2) {
    throw new Error(`长度不匹配: 期望${expectedLength}字节, 实际${hex.length / 2}`);
  }
}

export function hexToBytes(hex: string): Uint8Array {
  assertHex(hex);
  return _hexToBytes(hex);
}

export function bytesToHex(bytes: Uint8Array): string {
  return "0x" + _bytesToHex(bytes);
}

export function bigintToHex(n: bigint): string {
  if (n === 0n) return "0x0";
  return "0x" + n.toString(16);
}

export function hexToBigint(hex: string): bigint {
  assertHex(hex);
  return BigInt(hex);
}

export function bufBE(n: bigint): Uint8Array {
  if (n === 0n) return new Uint8Array([]);
  const hex = n.toString(16);
  const even = hex.length % 2 === 0 ? hex : "0" + hex;
  return _hexToBytes("0x" + even);
}

export function parseEther(eth: string): bigint {
  const [intPart, fracPartRaw] = eth.split(".");
  const fracPart = (fracPartRaw || "").slice(0, 18);
  const intWei = BigInt(intPart) * 10n ** 18n;
  const fracWei = fracPart ? BigInt(fracPart.padEnd(18, "0")) : 0n;
  return intWei + fracWei;
}

export function formatEther(wei: bigint): string {
  const sign = wei < 0n ? "-" : "";
  const v = wei < 0n ? -wei : wei;
  const intPart = v / (10n ** 18n);
  const fracPart = v % (10n ** 18n);
  const fracStr = fracPart.toString().padStart(18, "0").replace(/0+$/, "");
  return sign + intPart.toString() + (fracStr ? "." + fracStr : "");
}

export function toChecksumAddress(addrHex: string): string {
  // 输入必须是 0x + 40个十六进制字符
  assertHex(addrHex, 20);
  const lower = addrHex.slice(2).toLowerCase();
  const hash = bytesToHex(keccak256(new TextEncoder().encode(lower))).slice(2);
  let out = "0x";
  for (let i = 0; i < lower.length; i++) {
    const nibble = parseInt(hash[i], 16);
    out += nibble >= 8 ? lower[i].toUpperCase() : lower[i];
  }
  return out;
}